package com.xdf.wxbebarrel.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.thoughtworks.xstream.XStream;
import com.xdf.wxbebarrel.dao.flux.CallMapper;
import com.xdf.wxbebarrel.model.*;
import com.xdf.wxbebarrel.model.templateMessage.MetaData;
import com.xdf.wxbebarrel.model.templateMessage.TemplateMessageData;
import com.xdf.wxbebarrel.model.templateMessage.TemplateMessageWrapper;
import com.xdf.wxbebarrel.model.templateMessage.TextMessage;
import com.xdf.wxbebarrel.model.wxmsgcontent.Deliver;
import com.xdf.wxbebarrel.service.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Component
public class WechatUtils {
    private static final String TEMPLATE_ID_AUDIT = "IRUGdjkPP2p8sOXR-g-xtDsnvGkD_hyt-W083VeNo-I";
    @Autowired
    private ReqsLogService dbLog;
    private static final ObjectMapper mapper = new ObjectMapper();
    private static final String WECHAT_TOKENURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    private static WechatAccessToken at;
    @Autowired
    private WxJsApiOfficial jsApiOfficial;

    @Autowired
    private WechatOpe ope;

    @Autowired
    private UserService userService;
    @Autowired
    private QrloginService qrloginService;
    @Autowired
    private QrLogService qrLogService;
    @Autowired
    private CallMapper callMapper;
    @Autowired
    private MillService millService;
    @Autowired
    private UserMillService userMillService;

    // private static final String FRONT_END_URL= "https://wxshunjie.barrel.fit";
    // private static final String REAL_NAME_URL = "wxauthorize.html";// 填写真实姓名的网页名称
    public static final String GUEST = "游客";// 没有所属客户的新微信用户都归纳到该名称下
    @Autowired
    private WxAccessToken wxAccessToken;

    public String getResponse(InputStream is) throws IOException {
        dbLog.insert(is.toString(),"进入工具类的响应方法中");
        Map<String,String> map = parseRequest(is);
        dbLog.insert(map.toString(),"消息内容");
        String msgType = map.get("MsgType");
        WechatBaseMassage bm = null;

        switch (msgType){
            case "text":
                bm = resText(map);
                break;
            case "image":
                break;
            case "voice":
                break;
            case "video":
                break;
            case "shortvideo":
                break;
            case "location":
                break;
            case "link":
                break;
            case "event":// 处理事件，多数情况是处理扫描关注
                dbLog.insert("进入event响应中");
                bm = resEvent(map);
                break;
            default:
                break;
        }

        XStream stream = new XStream();
        // 按照指定类型将对象序列化为 xml
        stream.processAnnotations(WechatTextMessage.class);
//        stream.processAnnotations(Article.class);
//        stream.processAnnotations(ImgMessage.class);
//        stream.processAnnotations(Music.class);
//        stream.processAnnotations(MusicMessage.class);
//        stream.processAnnotations(NewsMessage.class);
//        stream.processAnnotations(VideoMessage.class);
//        stream.processAnnotations(VoiceMessage.class);

        String retXML = "";
        if ( bm == null ) return "";
        retXML = stream.toXML(bm);
        return retXML;

    }

    /**
     * 相应用户事件，返回 WechatBaseMassage
     * @param map
     * @return
     * @throws IOException
     */
    private WechatBaseMassage resEvent(Map<String,String> map) throws IOException {
        String event = map.get("Event");
        WechatBaseMassage bm = null;

        switch (event){
            case "subscribe":// 关注事件
                //subscribe(map);
//                bm = getRealNameLink(map);
                //bm = getRealNameUrlTextMsg(map);
                pushMsg2RealNamePage(map);
                break;
            case "unsubscribe":// 取消关注事件
//                me.eb.doSomethingAfterUnsubscribe(map);
                break;
            case "CLICK":// 点击菜单项事件
//                handleClick(map);
                break;
            case "SCAN":// 扫描二维码事件
//                me.eb.doSomethingAfterScanEntry(map);
                pushMsg2RealNamePage(map);// 处理第二次关联
                doAfterScan(map);// 处理登录扫描
                break;
            default:
                break;
        }
        return bm;
    }

    // 执行订阅逻辑
    private void subscribe(Map<String,String> map) {
        dbLog.insert(map.toString(),"关注时传入的用户信息");
        if( map == null ) throw new BusinessException("执行订阅逻辑时传入空map!");
        String openId = Optional.ofNullable(map.get("FromUserName")).orElse("");
        if( openId.length()<=0 ) throw new BusinessException("openid为空，无法获取微信用户信息！");
        //WechatUserInfo userInfo = getUserInfo(openId);// 根据openid请求微信服务器获取用户信息
        // 向 wechat.reqs_log 中写入当前微信用户的信息
        //dbLog.insert(userInfo.toString(),"根据openid获得的微信用户信息");
        //ope.subscribe(userInfo);// 向表 wechat.user 写入当前用户

        String qrScene = Optional.ofNullable(getQRScene(map)).orElse("");
        // 向表 user、user_mill 写入数据
        //ope.subscribe(userInfo,qrScene);
    }


    public static WechatTextMessage resText(Map<String,String> map){
        String content = map.get("Content");
        StringBuilder sb = new StringBuilder();
        switch (content){
            case "测试":
                sb.append("精之研测试 WechatTextMessage 成功");
                break;
            case "抬头":
                sb.append("精之研");
                break;
            case "测试日志":
                sb.append("");
                break;
            case "测试业务数据库":
                sb.append("");
                break;
            default:
                sb.append("");
                break;
        }

        String text = sb.toString();
        return (text.length()<=0)?null:new WechatTextMessage(map,sb.toString());
    }


    /**
     * 用户发送来的 http 请求中将 InputStream 类型数据
     * 转换为 Map 类型并返回，之后可以通过查询键值 MsgType
     * 得知用户发来的是什么类型的请求
     * 用户在服务号发送文本则 MsgType = text
     * 发送图片 MsgType = image
     * 声音 MsgType = voice
     * video = 视频,shortvideo = 短视频,location = 位置，link = 连接
     * event 表示事件，又分为多种事件：subscribe、unsubscribe、CLICK、SCAN
     * 依次表示：关注、取消关注、点击菜单、扫描服务号二维码（即使关注过了也会触发本事件）
     * @param is 用户发送来的请求的数据
     * @return 返回类型 Map<String,String> 的数据
     */
    public static Map<String, String> parseRequest(InputStream is ) {
        Map<String, String> map = new HashMap<>();
        SAXReader reader = new SAXReader();
        try {
            Document doc = reader.read(is);
            Element root = doc.getRootElement();
            List<Element> eles = root.elements();

            for (Element e : eles) {
                map.put(e.getName(), e.getStringValue());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }

        return map;
    }

    /**
     * 腾讯公司发给自己的服务器 get 请求验证合法性时用到
     * 返回给腾讯服务器正确的验证结果之后才可以正常运行服务号的后台功能
     * 服务号配置的 get 请求中调用本方法
     * @param timestamp 时间戳
     * @param nonce 附加文本
     * @param signature 验证信息
     * @return
     */
    public boolean check(String timestamp,String nonce,String signature) {
        // 1. 将token、timestamp、nonce三个参数进行字典序排序
        String[] strs = new String[] { DevMaterial.JZY.TOKEN,timestamp,nonce };

        //2. 将数组中的元素排序后形成字符串等待加密
        Arrays.sort(strs);
        String str = strs[0] + strs[1] + strs[2];

        // 3. 加密后的字符串与signature对比，相同则来源于微信
        String ciphertext = sha1(str);
        dbLog.insert(signature,"腾讯服务器传递来signature");
        dbLog.insert(ciphertext,"本地加密后的密文");
        return ciphertext.equalsIgnoreCase(signature);
    }

    /**
     * 进行 SHA1 加密，返回加密后的字符串
     * @param src 被加密的文本
     * @return 返回加密后的密文
     */
    private static String sha1(String src) {
        try {
            MessageDigest md = MessageDigest.getInstance("sha1");
            byte[] digest = md.digest(src.getBytes());
            char[] chars = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };

            StringBuilder sb = new StringBuilder();
            for( byte b:digest ) {
                sb.append(chars[(b>>4)&15]);
                sb.append(chars[b&15]);
            }

            return sb.toString();
        } catch (NoSuchAlgorithmException e) {

            e.printStackTrace();
        }

        return null;
    }

//    private void getNewAccessToken() {
//        dbLog.insert("进入请求新 token 的方法");
//        String url = WECHAT_TOKENURL.replace("APPID", DevMaterial.JZY.APPID).replace("APPSECRET", DevMaterial.JZY.APPSECRET);
////        String szToken = HttpUtils.sendGet(url,"");
//        String szToken = HttpUtils.sendGet(url);
//
//        dbLog.insert(url,"请求 token 的URL");
//        dbLog.insert(szToken,"获取到的 token");
//
//        JavaType jvt = mapper.getTypeFactory().constructParametricType(HashMap.class,String.class,String.class);
//        Map<String,String> jsonObject = null;
//        try {
//            jsonObject = mapper.readValue(szToken, jvt);
//        }catch (IOException e){
//            throw new BusinessException("请求新 token 时转换为 map 对象时出现异常！");
//        }
//
//        dbLog.insert(jsonObject.toString(),"token转换为json对象");
//
//        String access_token="";
//        String expire_in = "";
//        try {
//            access_token = jsonObject.get("access_token");
//            expire_in = jsonObject.get("expires_in");
//            dbLog.insert(access_token,"从腾讯请求来的token");
//            dbLog.insert(expire_in,"过期时间");
//        } catch (Exception e) {
//            String errCode = jsonObject.get("errcode");
//            StringBuilder sb = new StringBuilder();
//
//            switch (errCode) {
//                case "40164":
//                    sb.append(jsonObject.get("errmsg"));
//                    System.out.println("将IP地址添加到白名单：" + sb.toString());
//                    dbLog.insert(sb.toString(),"将该IP地址添加到微信服务号的白名单后再次尝试");
//                    return;
//                default:
//                    dbLog.insert("向微信服务器请求token出现未知的异常！");
//                    break;
//            }
//        }
//
//
//        at = new WechatAccessToken(access_token, expire_in);
//        dbLog.insert(at.toString(),"最终获取到的token");
//    }

    public List<WxJsApiConfig1> getWxJsApiConfigs(String url) throws IOException {
        dbLog.insert(url,"请求 wx js api 的URL");
        List<WxJsApiConfig1> list = jsApiOfficial.getConfigs(url);
        dbLog.insert(list.toString(),"返回的配置数据");
        return list;
    }

    /**
     * 获取微信用户的基本信息
     * @param openId 公众号中用户的 openId
     * @return
     */
    public WechatUserInfo getUserInfo(String openId)  {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
        String token = wxAccessToken.getAccessToken();
        url = url.replace("ACCESS_TOKEN",token).replace("OPENID",openId);
        String jsonString =  HttpUtils.sendGet(url);

        WechatUserInfo user = null;
        try {
            user = mapper.readValue(jsonString,WechatUserInfo.class);
        } catch (JsonProcessingException e) {
            throw new BusinessException("根据openid请求用户信息转换为WechatUserInfo对象时出现异常！");
        }
        return user;
    }


    private void pushMsg(TemplateMessageWrapper msg, List<String> openIds,String tmp) throws Exception {
        if( msg == null ) {
            dbLog.insert("推送模板消息时传入了空消息");
            throw new BusinessException("空消息不可推送！");
        }
        if( openIds == null || openIds.size()<=0 ) {
            dbLog.insert("推送模板消息时传入的微信 openid 是空");
            throw new BusinessException("没有设置接收推送消息的微信opend，无法推送！");
        }

        dbLog.insert(msg.toString(),"推送的模板消息");
        dbLog.insert(openIds.toString(),"模板消息推送给的微信号");

        XStream stream = new XStream();
        stream.processAnnotations(TemplateMessageWrapper.class);

        for(String item:openIds){
            dbLog.insert("循环中推送给" + item);
            msg.setTouser(item);

            String jsonString = mapper.writeValueAsString(msg);
            String szToken = wxAccessToken.getAccessToken();
            String reqUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + szToken;
            String ret = HttpUtils.postJson( reqUrl, jsonString);
            dbLog.insert("推送返回的结果是:" + ret);
        }
    }

    /**
     * 推送模板消息
     * @param msg 消息体
     * @param auditors 推送的目标微信 openId 构成的集合
     * @throws Exception
     */
    private void pushMsg(TemplateMessageWrapper msg, List<User> auditors) throws Exception {
        if( msg == null ) {
            dbLog.insert("推送模板消息时传入了空消息");
            throw new BusinessException("空消息不可推送！");
        }
        if( auditors == null || auditors.size()<=0 ) {
            dbLog.insert("推送模板消息时传入的微信 openid 是空");
            throw new BusinessException("没有设置接收推送消息的微信opend，无法推送！");
        }

        dbLog.insert(msg.toString(),"推送的模板消息");
        dbLog.insert(auditors.toString(),"模板消息推送给的微信号");

        XStream stream = new XStream();
        stream.processAnnotations(TemplateMessageWrapper.class);

        for(User auditor:auditors){
            dbLog.insert("循环中推送给" + auditor.getOpenId());
            msg.setTouser(auditor.getOpenId());

            String jsonString = mapper.writeValueAsString(msg);
            String szToken = wxAccessToken.getAccessToken();
            String reqUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + szToken;
            String ret = HttpUtils.postJson( reqUrl, jsonString);
            dbLog.insert("推送返回的结果是:" + ret);
        }
    }

    /**
     * 其他第三方程序可以调用本方法发送模板消息给微信用户
     * ExternalTemplateMsg 包含了模板消息的内容、模板ID，以及接受消息的 openid
     * @param record
     * @throws Exception
     */
    public Boolean pushMsgExternalGeneral(ExternalTemplateMsg record) throws Exception {
        if( record == null ) {
            dbLog.insert("传入了空的ExternalTemplateMsg对象，不会推送模板消息");
            return false;
        }

        List<MetaData> metaList = record.getMetaList();
        if( metaList == null || metaList.size()<=0 ){
            dbLog.insert("通用外部推送模板消息时，消息体为空，不会推送消息。");
            return false;
        }

        TemplateMessageWrapper msg = strArray2TemplateMessageMetaData(metaList);
        msg.setTemplate_id(record.getTemplateId());
        pushMsg(msg,record.getOpenidList(),"");
        return true;
    }

    public void pushMsgDeliver(Deliver msgContent, List<String> openIds) throws Exception {
        if( msgContent == null ) return;
        if( openIds == null || openIds.size()<=0 ) return;

        String billCode = "发货单据编号：" + Optional.ofNullable(msgContent.getBillCode()).orElse("");
        String client = Optional.ofNullable(msgContent.getClientName()).orElse("") + " , " + Optional.ofNullable(msgContent.getDyeName()).orElse("");
        String goodsName = Optional.ofNullable(msgContent.getGoodsName()).orElse("");
        String qty = Optional.ofNullable(msgContent.getPs()).orElse(0).toString() + "匹 / "
                + Optional.ofNullable(msgContent.getKg()).orElse(BigDecimal.valueOf(0)).toString() + "公斤";
        String specification = Optional.ofNullable(msgContent.getSpecification()).orElse("");
        String datetime = DatetimeUtils.formatDate(msgContent.getBillDate());


//        String[] strArray = { msgContent.getBillCode(),msgContent.getBillDate().toString(),
//                msgContent.getClientName() + "," + msgContent.getDyeName(),msgContent.getGoodsName(),
//                msgContent.getSpecification(),msgContent.getPs().toString(),msgContent.getKg().toString(),"" };
        String detailUrl = "https://www.jzy.world/billDeliver?mainId=" + msgContent.getIid().toString();
        String[] strArray = {billCode,client,goodsName,qty,specification,datetime,"",detailUrl};
        TemplateMessageWrapper msg = strArray2TemplateMessage(strArray);
        msg.setTemplate_id(SysConst.TEMPLATE_MESSAGE_ID_DELIVER);// 指定模板消息的编号
        pushMsg(msg, openIds,"");
    }

    // 传入微信用户的 openid ,推送给管理员审核用模板消息
    private void pushMsgAudit(String openId){
        WechatUserInfo userInfo = getUserInfo(openId);// 根据openid请求微信服务器获取用户信息
        String nickName = Optional.ofNullable(userInfo.getNickname()).orElse("");
        try {// 推送给管理员审核用模板消息
            pushMsgAudit(openId,nickName,nickName);
        }catch (Exception e){
            dbLog.insert("推送待审核模板消息时出现异常");
        }
    }

    /**
     * 2021年7月6日 08:30:30
     * chanchaw
     * 从3个参数的同名方法拷贝来，向 mill 内指定的拥有审核权限的微信用户推送审核消息
     * 消息中的连接为打开配置权限的页面
     * @param openId
     * @param nickName
     * @param realName
     * @param millIid
     * @throws Exception
     */
    public void pushMsgAudit(String openId,String nickName,String realName,Integer millIid) throws Exception {
         List<User> auditors = userService.selectAuditors();
        //List<User> auditors = callMapper.selectAuditorsInMill(mill);
        if( auditors == null || auditors.size()<=0 ){
            dbLog.insert("推送审核用模板消息时没有获取到拥有审核权限的微信用户！");
            throw new BusinessException("无拥有审核权限的微信用户，推送审核用模板消息失败！");
        }

        dbLog.insert(auditors.toString(),"即将推送审核用模板消息的微信用户");

        // 根据名称获取 mill
        Mill mill = millService.selectByPrimaryKey(millIid);
        if(mill == null) throw new BusinessException("未找到该名称的客户！");

        // 字符串数组转换为模板消息对象
        //String url = "https://www.jzy.world/audit?openId=" + openId + "&nickName=" + nickName;
        // 顺杰的客户英文名称中会有 & 符号，导致前端页面获取到的公司名称被截断，这里传 mill.iid ，前端需要再次查询获取名称
//        String url = SysConst.FRONT_END_URL + "/registernewuser.html?openId=" + openId + "&nickName=" + nickName + "&realName=" +realName + "&millName=" + mill;
        String url = SysConst.FRONT_END_URL + "/registernewuser.html?openId=" + openId
                + "&nickName=" + nickName + "&realName=" +realName + "&millIid=" + millIid;
        url = getWechatProxyUrl(url);// 使用微信代理地址，在创建新用户数据时要使用code获取管理员微信
        // String[] strArray = {openId,nickName,realName,"","","","",url};
        String[] strArray = {"点击本通知审核新用户注册的请求",openId,nickName,realName,mill.getSid(),"","",url};
        TemplateMessageWrapper msg = strArray2TemplateMessage(strArray);
        msg.setTemplate_id(SysConst.TEMPLATE_MESSAGE_ID_AUDIT_MILL);// 指定模板消息的编号
        dbLog.insert(msg.toString(),"审核用模板消息");

        pushMsg(msg, auditors);
    }


    //推送用于审核的模板消息，本方法构建好消息的内容以及模板编号
    private void pushMsgAudit(String openId,String nickName,String realName) throws Exception {
        List<User> auditors = userService.selectAuditors();
        if( auditors == null || auditors.size()<=0 ){
            dbLog.insert("推送审核用模板消息时没有获取到拥有审核权限的微信用户！");
            throw new BusinessException("无拥有审核权限的微信用户，推送审核用模板消息失败！");
        }

        dbLog.insert(auditors.toString(),"即将推送审核用模板消息的微信用户");
        // 字符串数组转换为模板消息对象
        //String url = "https://www.jzy.world/audit?openId=" + openId + "&nickName=" + nickName;
        String url = SysConst.FRONT_END_URL + "/audit?openId=" + openId + "&nickName=" + nickName;;
        String[] strArray = {openId,nickName,realName,"","","","",url};
        dbLog.insert("接下来4条信息是用于构建审核用模板消息的数据");
        dbLog.insert(openId,"openid");
        dbLog.insert(nickName,"nickName");
        dbLog.insert(realName,"realName");
        dbLog.insert(url,"url");
//        dbLog.insert(strArray.toString(),"用于构建消息的字符串数组");
//        List<String> list = new ArrayList<>();
//        list.add(openId);list.add(nickName);list.add(realName);list.add("");
//        list.add("");list.add("");list.add("");list.add("");
//        String[] strArray = list.toArray();
        TemplateMessageWrapper msg = strArray2TemplateMessage(strArray);
        msg.setTemplate_id(SysConst.TEMPLATE_MESSAGE_ID_AUDIT);// 指定模板消息的编号
        dbLog.insert(msg.toString(),"审核用模板消息");

        pushMsg(msg, auditors);
    }

    // 传入模板消息的 MetaData 对象，设置默认字体颜色后返回该对象
    private MetaData setDefaultColor(MetaData record){
        String color = Optional.ofNullable(record.getColor()).orElse(SysConst.TEMPLATE_MESSAGE_BLUE);
        record.setColor(color);
        return record;
    }

    private TemplateMessageWrapper strArray2TemplateMessageMetaData(List<MetaData> metaList){
        if( metaList == null || metaList.size()<=0 ){
            dbLog.insert("字符串数组转换为模板消息时传入了空数组！");
            throw new BusinessException("空消息体无法推送用于审核的模板消息！");
        }

        // 由于每个模板的要素个数不一致，这里不再检测要素的个数
        // 2021年2月18日 22:42:19 仅使用了一个“成为会员通知”作为审核用模板，该模板是将详情页 url 计算入内为8个要素
//        if( strArray.length != SysConst.TEMPLATE_MESSAGE_DATA_LENGTH ) {
//            dbLog.insert("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//            throw new BusinessException("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//        }

        dbLog.insert(metaList.toString(),"等待转换为模板消息的字符串数组");

        // 加工外部传入的 MetaData ，设置默认字体颜色
        // 只有最后一个序号7的没有颜色 - 详情页的连接地址，所以没有颜色
        TemplateMessageData data = new TemplateMessageData();
        data.setFirst(setDefaultColor(metaList.get(0)));
        data.setKeyword1(setDefaultColor(metaList.get(1)));
        data.setKeyword2(setDefaultColor(metaList.get(2)));
        data.setKeyword3(setDefaultColor(metaList.get(3)));
        data.setKeyword4(setDefaultColor(metaList.get(4)));
        data.setKeyword5(setDefaultColor(metaList.get(5)));
        data.setRemark(setDefaultColor(metaList.get(6)));
        data.setUrl(metaList.get(7).getValue());// 详情页的地址（字符串），所以没有颜色

        dbLog.insert(data.toString(),"TemplateMessageData");

        TemplateMessageWrapper msg = new TemplateMessageWrapper();
        msg.setData(data);
        msg.setMiniprogram(null);
        msg.setUrl(metaList.get(7).getValue());

        dbLog.insert(msg.toString(),"字符串转换后的模板消息");
        return msg;
    }

    private TemplateMessageWrapper strArray2TemplateMessage(List<String> strList){
        if( strList == null || strList.size()<=0 ){
            dbLog.insert("字符串数组转换为模板消息时传入了空数组！");
            throw new BusinessException("空消息体无法推送用于审核的模板消息！");
        }

        // 由于每个模板的要素个数不一致，这里不再检测要素的个数
        // 2021年2月18日 22:42:19 仅使用了一个“成为会员通知”作为审核用模板，该模板是将详情页 url 计算入内为8个要素
//        if( strArray.length != SysConst.TEMPLATE_MESSAGE_DATA_LENGTH ) {
//            dbLog.insert("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//            throw new BusinessException("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//        }

        dbLog.insert(strList.toString(),"等待转换为模板消息的字符串数组");

        TemplateMessageData data = new TemplateMessageData();
        data.setFirst(new MetaData(strList.get(0)));
        data.setKeyword1(new MetaData(strList.get(1)));
        data.setKeyword2(new MetaData(strList.get(2)));
        data.setKeyword3(new MetaData(strList.get(3)));
        data.setKeyword4(new MetaData(strList.get(4)));
        data.setKeyword5(new MetaData(strList.get(5)));
        data.setRemark(new MetaData(strList.get(6),SysConst.TEMPLATE_MESSAGE_GRAY));
        data.setUrl(strList.get(7));

        dbLog.insert(data.toString(),"TemplateMessageData");

        TemplateMessageWrapper msg = new TemplateMessageWrapper();
        msg.setData(data);
        msg.setMiniprogram(null);
        msg.setUrl(strList.get(7));

        dbLog.insert(msg.toString(),"字符串转换后的模板消息");
        return msg;
    }


    /**
     * 将字符串数组转换为 TemplateMessageWrapper
     * @param strArray
     * @return
     */
    private TemplateMessageWrapper strArray2TemplateMessage(String[] strArray){
        if( strArray == null || strArray.length<=0 ){
            dbLog.insert("字符串数组转换为模板消息时传入了空数组！");
            throw new BusinessException("空消息体无法推送用于审核的模板消息！");
        }

        // 由于每个模板的要素个数不一致，这里不再检测要素的个数
        // 2021年2月18日 22:42:19 仅使用了一个“成为会员通知”作为审核用模板，该模板是将详情页 url 计算入内为8个要素
//        if( strArray.length != SysConst.TEMPLATE_MESSAGE_DATA_LENGTH ) {
//            dbLog.insert("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//            throw new BusinessException("传入的模板消息字符串数组长度要求为8，当前是" + strArray.length);
//        }

        dbLog.insert(strArray.toString(),"等待转换为模板消息的字符串数组");

        TemplateMessageData data = new TemplateMessageData();
        data.setFirst(new MetaData(strArray[0]));
        data.setKeyword1(new MetaData(strArray[1]));
        data.setKeyword2(new MetaData(strArray[2]));
        data.setKeyword3(new MetaData(strArray[3]));
        data.setKeyword4(new MetaData(strArray[4]));
        data.setKeyword5(new MetaData(strArray[5]));
        data.setRemark(new MetaData(strArray[6],SysConst.TEMPLATE_MESSAGE_GRAY));
        data.setUrl(strArray[7]);

        dbLog.insert(data.toString(),"TemplateMessageData");

        TemplateMessageWrapper msg = new TemplateMessageWrapper();
        msg.setData(data);
        msg.setMiniprogram(null);
        msg.setUrl(strArray[7]);

        dbLog.insert(msg.toString(),"字符串转换后的模板消息");
        return msg;
    }

    public String getOpenidByOAuth(String code) throws IOException {
        dbLog.insert(code,"使用code请求openid");
        String openidUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
        String requestUrl = openidUrl.replace("APPID", DevMaterial.JZY.APPID).replace("APPSECRET", DevMaterial.JZY.APPSECRET).replace("CODE", code);

        dbLog.insert(requestUrl,"用于请求openid的url");
        String result = HttpUtils.sendGet(requestUrl);
        dbLog.insert(result,"使用code通过get请求来的字符串结果");
        WebAuthorizeResult openidResult = mapper.readValue(result,WebAuthorizeResult.class);
        WechatWebAuthorityUtils.resolveErrorCode(openidResult.getErrcode());// 处理错误代码
        if( openidResult == null ){
            dbLog.insert("将请求来的字符串结果装换为WebAuthorizeResult对象后为空");
            throw new BusinessException("使用code请求openid发送get请求后获得了空数据");
        }

        dbLog.insert(openidResult.getOpenid(),"请求来的openid");
        dbLog.insert(openidResult.getAccess_token(),"请求来的 Access_token");
        return openidResult.getOpenid();
    }

    /**
     * 获取QR二维码兑换用票据
     * @param scene 场景值，用于区分是在平板登录或者PC浏览器登录
     * @return 返回用于兑换二维码的票据 - 有效时间1分钟
     * @throws IOException
     */
    public String getQRTicketTemporary(String scene) throws IOException {
        dbLog.insert("开始生成临时二维码兑换票据");
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
        String accessToken = Optional.ofNullable(wxAccessToken.getAccessToken()).orElse("");
        dbLog.insert("用于请求票据的 token:" + accessToken);
        url = url.replace("TOKEN", accessToken);


        // ticket 中标记了二维码的有效期是7天 = 604800，2分钟 = 120，这里的计算单位是秒
        String data = "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\":\"" + scene + "\"}}}";
        dbLog.insert("请求票据的字符串是：" + data);
        String result = HttpUtils.postForm(url, data,false);
        dbLog.insert("请求来的响应结果是：" + result);
        StringBuilder ticket = new StringBuilder();

        QRCodeTicketMsg msg = mapper.readValue(result,QRCodeTicketMsg.class);

        if(msg != null){
            ticket.append(msg.getTicket());
        }else{
            ticket.append("");
        }

        // 使用了失效的 accessToken 则微信返回下面错误
        // {"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest rid: 6384659c-7e152d90-156385d3"}
        // 则纠正为使用该最新有效的 token
        if(result.indexOf("40001")>=0){
            dbLog.insert(accessToken,"错误代码40001，使用了失效的token。即将请求新的token后重试");
            String newToken = Optional.ofNullable(wxAccessToken.getAccessToken()).orElse("");
            dbLog.insert(newToken,"40001后请求来的新token");
            url = url.replace("TOKEN", newToken);
            result = HttpUtils.postForm(url, data,false);
            dbLog.insert(result,"40001后使用新token请求二维码票据响应来的结果");
            msg = mapper.readValue(result,QRCodeTicketMsg.class);
            if(msg != null) ticket.append(msg.getTicket());
            else ticket.append("");
        }

        dbLog.insert("临时二维码兑换票据消息是:" + result);
        dbLog.insert("其中票号是:" + ticket.toString());
        return ticket.toString();
    }

    // 返回临时二维码图片的完整链接，需要传入场景值（任意字符串）
    public String getQRCodeTemporary(String scene) throws IOException {
        dbLog.insert("开始请求临时二维码，场景值是：" + scene);
        String ret = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + getQRTicketTemporary(scene);
        dbLog.insert("二维码连接是：" + ret);
        return ret;
    }

    private void doAfterScan(Map<String,String> map){
        dbLog.insert(map.toString(),"登录扫描的消息");
        String openid = Optional.ofNullable(map.get("FromUserName")).orElse("");

        // 1. 检测表 user 是否有该微信用户
        // 2. 检测表 user_mill 是否有关联关系
        // 没有关联关系则认为是无效用户，退出不继续，
        // 不会向 qr_log 写入数据也就无法登陆
        if( userService.effectiveUser(openid) == false)
            return;

        String guid = Optional.ofNullable(map.get("EventKey")).orElse("");
        // 向 white_account.qrlogin 中写入当前用户一条登录信息，WEB项目可读取该信息后执行后台登录
        //SysUser sysUser = qrloginService.login(openid,guid);
//        if( sysUser == null ){// 没有进入订阅方法内说明该微信号已经订阅过但是在业务数据库中没有注册
////            pushMsgAudit(openid);
//            sysUserService.insert8Openid(openid);// 新关注的用户在白坯系统的数据库 white_account 自动新增用户
//        }

        // 2021年6月10日 17:55:41 注释上面的代码
        // 此后修改为扫描二维码后向表 wechat.qr_log 写入数据
        String qrt = Optional.ofNullable(map.get("CreateTime")).orElse("");
        Date qrTime = null;
        try {
            long lt = new Long(qrt) * 1000;
            qrTime = new Date(lt);
        }catch (Exception e){
            qrTime = new Date();
        }

        String sceneStr = Optional.ofNullable(map.get("EventKey")).orElse("");
        String ticket = Optional.ofNullable(map.get("Ticket")).orElse("");
        QrLog record = new QrLog();
        record.setOpenid(openid);
        record.setQrTime(qrTime);
        record.setSceneStr(sceneStr);
        record.setTicket(ticket);
        qrLogService.save(record);

    }

    /**
     * 解析关注时的 map 消息，返回场景值
     * @param map 微信用户关注消息
     * @return 返回场景值
     */
    private String getQRScene(Map<String,String> map){
        dbLog.insert(map.toString(),"即将解析场景值的原始消息");
        if( map == null ) return null;
        String eventKey = map.get("EventKey");
        String ret = eventKey.replace("qrscene_","").trim();
        dbLog.insert(ret,"解析得到的场景值");
        return ret;
    }

    // 根据请求 map 数据获取 openId
    public static String getOpenIdFromReqMap(Map<String, String> reqMap){
        return reqMap.get("FromUserName");
    }

    // 构建并返回要求填写真实姓名的页面的连接
    public static TextMessage getRealNameLink(Map<String,String> map){
        String openId = getOpenIdFromReqMap(map);
        String qrscene = WechatResponseResolver.getScene(map);// = millName
        String page = "https://www.barrel.fit/wxfebarrel/wxauthorize.html?millName=" + qrscene;
        String codedPage = URLEncoder.encode(page);
        String link = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+DevMaterial.JZY.APPID+"&redirect_uri=" +
                codedPage + "&response_type=code&scope=snsapi_userinfo&state=chanchaw#wechat_redirect";
        return buildTextMsg(openId,link);
    }

    public static WechatTextMessage getRealNameUrlTextMsg(Map<String,String> map){
        String openId = getOpenIdFromReqMap(map);
        String qrscene = WechatResponseResolver.getScene(map);// = millName
        String page = "https://www.barrel.fit/wxfebarrel/wxauthorize.html?millName=" + qrscene;
        String codedPage = URLEncoder.encode(page);;
        String link = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+DevMaterial.JZY.APPID+"&redirect_uri=" +
                codedPage + "&response_type=code&scope=snsapi_userinfo&state=chanchaw#wechat_redirect";
        return new WechatTextMessage(map,link);
    }

    public static TextMessage buildTextMsg(String toUserName,String content){
        TextMessage msg = new TextMessage(DevMaterial.JZY.PLATEID,toUserName);
        msg.setContent(content);
        return msg;
    }

    // 推送给新用户模板消息，点击详情后跳转到填写真实姓名的页面
    public void pushMsg2RealNamePage(Map<String,String> map) {
        String qrscene = WechatResponseResolver.getScene(map);// = millName
//        String page = "https://www.barrel.fit/wxfebarrel/wxauthorize.html?millName=" + qrscene;
        if(qrscene == null || qrscene.length() == 0) return;// 关注或者扫码中没有场景值则不做任何回应
        if(!isMill(qrscene)) return; // 根据场景值查询没有对应客户则不做任何回应

        Mill mill = millService.selectBySid(qrscene);
        String openId = getOpenIdFromReqMap(map);
        if(hasRelationShip(openId,qrscene)) return;// 当前openId和qrscene(mill)已经有映射关系了则不再进行申请注册
//        String page = SysConst.REAL_NAME_URL_FULL + "?millName=" + qrscene;
        String page = SysConst.REAL_NAME_URL_FULL + "?millIid=" + mill.getIid();
        String codedPage = URLEncoder.encode(page);
        String realNamePageUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+DevMaterial.JZY.APPID+"&redirect_uri=" +
                codedPage + "&response_type=code&scope=snsapi_userinfo&state=chanchaw#wechat_redirect";

        TemplateMessageWrapper wrapper = new TemplateMessageWrapper();
        wrapper.setUrl(realNamePageUrl);

        TemplateMessageData data = new TemplateMessageData();
        data.setFirst(new MetaData("******点击【查看详情】进入注册页面******",SysConst.TEMPLATE_MESSAGE_RED));
        data.setKeyword1(new MetaData(qrscene,SysConst.TEMPLATE_MESSAGE_BLUE));
        data.setKeyword2(new MetaData(DatetimeUtils.formatNow(),SysConst.TEMPLATE_MESSAGE_BLUE));
        data.setKeyword3(new MetaData(openId,SysConst.TEMPLATE_MESSAGE_BLUE));
        wrapper.setData(data);
        // wrapper.setTemplate_id(TEMPLATE_ID_AUDIT);
        wrapper.setTemplate_id(SysConst.TEMPLATE_MESSAGE_ID_AUDIT_MILL);
        ArrayList<String> openIdList = new ArrayList<>();
        openIdList.add(openId);
        try {
            pushMsg(wrapper,openIdList,"");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
        String token1 = url.replace("TOKEN", "111");
        String token2 = url.replace("TOKEN", "222");
        System.out.println(token1);
        System.out.println(token2);
    }

    /**
     * 推送模板消息 nLH0PRB1bu2zUqqlpxzMUBM3slaWBp_r_WFDjeolNuY
     * @param openId 推送给的目标微信的 openId
     * @param msgStrArray 推送的模板消息的字符串数组
     * @param templateId 模板ID
     */
    public void pushMsg(String openId,String[] msgStrArray,String templateId) {
        if(templateId == null || templateId.length() == 0) templateId = "nLH0PRB1bu2zUqqlpxzMUBM3slaWBp_r_WFDjeolNuY";
        TemplateMessageWrapper msg = strArray2TemplateMessage(msgStrArray);
        msg.setTemplate_id(templateId);// 指定模板消息的编号
        User user = userService.selectByOpenId(openId);
        ArrayList<User> users = new ArrayList<>();
        users.add(user);
        try {
            pushMsg(msg,users);
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }

    public static String getWechatProxyUrl(String url){
        String encodeUrl = URLEncoder.encode(url);
//        return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+DevMaterial.JZY.APPID+"&redirect_uri=" +
//                encodeUrl + "&response_type=code&scope=snsapi_userinfo&state=chanchaw#wechat_redirect";
        return "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+DevMaterial.JZY.APPID+"&redirect_uri=" +
                encodeUrl + "&response_type=code&scope=snsapi_base&state=chanchaw#wechat_redirect";
    }

    private Boolean isMill(String millName){
        Mill mill = millService.selectBySid(millName);
        return mill == null ? false : true;
    }

    private Boolean hasRelationShip(String openId,String millName){
        UserMill userMill = userMillService.selectUserMill(openId, millName);
        Boolean ret = userMill == null ? false : true;
        if(!ret) return ret;
        // 推送给用户提示已经有针对该客户的映射关系了，无需重复申请
        // pushMsgExternalGeneral()
        ExternalTemplateMsg msg = new ExternalTemplateMsg();

        ArrayList<String> openIdList = new ArrayList<>();
        openIdList.add(openId);

        ArrayList<MetaData> metaDataList = new ArrayList<>();
        metaDataList.add(MetaData.builder().value("已存在关联关系").build());
        metaDataList.add(MetaData.builder().value("已有同" + millName +"的关联关系" ).color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value(DatetimeUtils.formatNow()).color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value("无需重复申请").color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value("未提交给管理员消息").color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value("").color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value("").color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        metaDataList.add(MetaData.builder().value("").color(SysConst.TEMPLATE_MESSAGE_BLUE).build());
        msg.setMetaList(metaDataList);
        msg.setTemplateId(SysConst.TEMPLATE_MESSAGE_ID_HAS_RELATIONSHIP);

        try {
            pushMsgExternalGeneral(msg);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return ret;
    }
}
